package com.jd.dlc.limit.scan;

import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.jd.dlc.annotation.DlcLimiter;
import com.jd.dlc.autoconfigure.DlcProperties;
import com.jd.dlc.limit.Limiter;
import com.jd.dlc.limit.fallback.backer.Backer;
import com.jd.dlc.spi.SPIFactory;
import com.jd.dlc.spring.SpringUtils;
import com.jd.dlc.util.AnnotationUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @program: dlc
 * @ClassName Sanner Packet Scan
 * @description:
 * @author: sizegang
 * @create: 2022-05-10
 **/
@Component
public class Sanner {
    /**
     * Current limiting actuator
     */
    public static Map<String, Limiter> limits = new ConcurrentHashMap<>();

    private static final Logger logger = LoggerFactory.getLogger(Sanner.class);

    private static Set<Class> classSet = Sets.newHashSet();

    public static Map<String, Backer> backers = Maps.newHashMap();

    @Resource
    private SPIFactory spiFactory;

    @Resource
    DlcProperties dlcProperties;


    private Sanner() {
    }

    public static Map<String, Backer> getBackers() {
        return backers;
    }


    /**
     * class loader initialization
     *
     * @throws IOException
     * @throws ClassNotFoundException
     */
    public void init() {
        try {
            /**
             * scan package
             */
            dlComponentScanInit();
            /**
             * init
             */
            initSyskeBootContent();
        } catch (Exception e) {
            logger.error("dlc package scan error {}", e);
        }

        logger.error("dlc package scan success！！！");
    }

    @Resource
    private ScanSupport support;

    /**
     * Scan the specified package path. If there is no such path,
     * the path where the server core entry is located will be scanned by default.
     *
     * @throws IOException
     * @throws ClassNotFoundException
     */
    private void dlComponentScanInit() {
        String[] sannerPath = dlcProperties.getLimiter().getSannerPath();
        for (String path : sannerPath) {
            Set<Class> clazzFromAnnotation = AnnotationUtil.getClazzFromAnnotation(path, DlcLimiter.class);
            if(clazzFromAnnotation != null){
                classSet.addAll(clazzFromAnnotation);
            }
        }
        if(classSet.isEmpty()){
            support.doScanClass();
        }


    }


    /**
     * Scan GuavaLimiter
     *
     * @throws IOException
     * @throws ClassNotFoundException
     */
    private void initSyskeBootContent() {
        logger.info("Start scanning GuavaLimiter annotations");
        long startTIme = System.currentTimeMillis();
        if (classSet == null || classSet.size() == 0) {
            return;
        }
        classSet.forEach(c -> {
            try {
                Method[] methods = c.getMethods();
                for (Method method : methods) {
                    backers(method);
                    limiterSPI(method);
                }
            } catch (Exception e) {
                logger.error("Container initialization error {}", e);
            }

        });
        long useTime = System.currentTimeMillis() - startTIme;
        logger.info("cost  scanning GuavaLimiter annotations", useTime);
    }


    private String getNameSpace(Method method, DlcLimiter dlcLimiter) {
        /**
         * spring many cases
         */
        String namespace = dlcLimiter.namespace() == null ? method.getName() : dlcLimiter.namespace();
        return namespace;
    }

    private void backers(Method method) {
        DlcLimiter dlcLimiter = AnnotationUtils.findAnnotation(method, DlcLimiter.class);
        if (dlcLimiter == null) {
            return;
        }
        try {
            String namespace = getNameSpace(method, dlcLimiter);
            backers.put(namespace, SpringUtils.getBean(dlcLimiter.limiterBacker(), Backer.class));
        } catch (Exception ex) {
            logger.error("method {} No limitBacker error{}", method.getName(), ex);
        }
    }

    public void limiterSPI(Method method) {
        DlcLimiter dlcLimiter = AnnotationUtils.findAnnotation(method, DlcLimiter.class);
        if (dlcLimiter == null) {
            return;
        }
        try {
            String namespace = getNameSpace(method, dlcLimiter);
            limits.put(namespace, spiFactory.getSPI(dlcProperties.getLimiter().getType(), Limiter.class));
        } catch (Exception ex) {
            logger.error("method {} No limitBacker error{}", method.getName(), ex);
        }
    }

    @PostConstruct
    private void initialize() {
        init();
    }
}
